package com.shxzz.easy12306.services.order.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.ListUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.toolkit.SqlHelper;
import com.shxzz.easy12306.framework.starter.common.dto.order.*;
import com.shxzz.easy12306.framework.starter.common.dto.user.UserQueryActualRespDTO;
import com.shxzz.easy12306.framework.starter.common.mq.event.DelayCloseOrderEvent;
import com.shxzz.easy12306.framework.starter.common.mq.event.PayResultCallbackOrderEvent;
import com.shxzz.easy12306.framework.starter.convention.exceptions.ClientException;
import com.shxzz.easy12306.framework.starter.convention.exceptions.RemoteException;
import com.shxzz.easy12306.framework.starter.convention.exceptions.ServiceException;
import com.shxzz.easy12306.framework.starter.convention.page.PageResponse;
import com.shxzz.easy12306.framework.starter.convention.result.Result;
import com.shxzz.easy12306.framework.starter.user.core.UserContext;
import com.shxzz.easy12306.services.order.common.constant.RedisKeyConstant;
import com.shxzz.easy12306.services.order.common.enums.OrderItemStatus;
import com.shxzz.easy12306.services.order.common.enums.OrderStatus;
import com.shxzz.easy12306.services.order.dao.entity.OrderDO;
import com.shxzz.easy12306.services.order.dao.entity.OrderItemDO;
import com.shxzz.easy12306.services.order.dao.entity.OrderItemPassengerDO;
import com.shxzz.easy12306.services.order.dao.mapper.OrderItemMapper;
import com.shxzz.easy12306.services.order.dao.mapper.OrderItemPassengerMapper;
import com.shxzz.easy12306.services.order.dao.mapper.OrderMapper;
import com.shxzz.easy12306.services.order.dto.domain.OrderItemStatusTransition;
import com.shxzz.easy12306.services.order.dto.req.TicketOrderPageQueryReqDTO;
import com.shxzz.easy12306.services.order.dto.req.TicketOrderSelfPageQueryReqDTO;
import com.shxzz.easy12306.services.order.dto.resp.TicketOrderDetailSelfRespDTO;
import com.shxzz.easy12306.services.order.mq.producer.DelayCloseOrderProducer;
import com.shxzz.easy12306.services.order.remote.UserRemoteService;
import com.shxzz.easy12306.services.order.service.OrderItemPassengerService;
import com.shxzz.easy12306.services.order.service.OrderItemService;
import com.shxzz.easy12306.services.order.service.OrderService;
import com.shxzz.easy12306.services.order.service.orderid.OrderIdGenerator;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.client.producer.SendStatus;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

/**
 * 订单服务层接口实现
 */
@Slf4j
@RequiredArgsConstructor
@Service
public class OrderServiceImpl implements OrderService {

    private final OrderIdGenerator orderIdGenerator;

    private final OrderMapper orderMapper;
    private final OrderItemMapper orderItemMapper;
    private final OrderItemPassengerMapper orderItemPassengerMapper;
    private final OrderItemService orderItemService;
    private final OrderItemPassengerService orderItemPassengerService;
    private final UserRemoteService userRemoteService;

    private final DelayCloseOrderProducer delayCloseOrderProducer;

    private final RedissonClient redissonClient;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public String createTicketOrder(TicketOrderCreateReqDTO requestParam) {
        String orderSn = orderIdGenerator.getOrderId(requestParam.getUserId());
        OrderDO orderDO = OrderDO.builder()
                .orderSn(orderSn)
                .userId(requestParam.getUserId())
                .username(requestParam.getUsername())
                .source(requestParam.getSource())
                .orderTime(requestParam.getOrderTime())
                .trainId(requestParam.getTrainId())
                .trainNumber(requestParam.getTrainNumber())
                .ridingDate(requestParam.getRidingDate())
                .departureTime(requestParam.getDepartureTime())
                .arrivalTime(requestParam.getArrivalTime())
                .departure(requestParam.getDeparture())
                .arrival(requestParam.getArrival())
                .status(OrderStatus.PENDING_PAYMENT.getStatus())
                .build();
        orderMapper.insert(orderDO);

        List<TicketOrderItemCreateReqDTO> ticketOrderItems = requestParam.getTicketOrderItems();
        List<OrderItemDO> orderItemDOList = ticketOrderItems.stream().map(ticketOrderItem ->
                OrderItemDO.builder()
                        .orderSn(orderSn)
                        .userId(requestParam.getUserId())
                        .username(requestParam.getUsername())
                        .trainId(requestParam.getTrainId())
                        .phone(ticketOrderItem.getPhone())
                        .realName(ticketOrderItem.getRealName())
                        .carriageNumber(ticketOrderItem.getCarriageNumber())
                        .seatNumber(ticketOrderItem.getSeatNumber())
                        .seatType(ticketOrderItem.getSeatType())
                        .idType(ticketOrderItem.getIdType())
                        .idCard(ticketOrderItem.getIdCard())
                        .amount(ticketOrderItem.getAmount())
                        .ticketType(ticketOrderItem.getTicketType())
                        .status(OrderItemStatus.PENDING_PAYMENT.getStatus())
                        .build()
        ).toList();
        orderItemService.saveBatch(orderItemDOList);

        List<OrderItemPassengerDO> orderItemPassengerDOList = ticketOrderItems.stream()
                .map(ticketOrderItem ->
                        OrderItemPassengerDO.builder()
                                .orderSn(orderSn)
                                .idType(ticketOrderItem.getIdType())
                                .idCard(ticketOrderItem.getIdCard())
                                .build()
                ).toList();
        orderItemPassengerService.saveBatch(orderItemPassengerDOList);
        // 延时关闭订单
        SendResult sendResult = delayCloseOrderProducer.sendMessage(DelayCloseOrderEvent.builder()
                .orderSn(orderSn)
                .trainId(requestParam.getTrainId().toString())
                .departure(requestParam.getDeparture())
                .arrival(requestParam.getArrival())
                .trainPurchaseTicketResults(ticketOrderItems)
                .build());
        if (!Objects.equals(sendResult.getSendStatus(), SendStatus.SEND_OK)) {
            throw new ServiceException("投递延迟订单消息失败");
        }
        return orderSn;
    }

    @Override
    public TicketOrderDetailRespDTO queryTicketOrderByOrderSn(String orderSn) {

        LambdaQueryWrapper<OrderDO> queryWrapper = Wrappers.lambdaQuery(OrderDO.class).eq(OrderDO::getOrderSn, orderSn);
        OrderDO orderDO = orderMapper.selectOne(queryWrapper);
        if (orderDO == null) {
            throw new ClientException("订单号无效");
        }
        LambdaQueryWrapper<OrderItemDO> orderItemQueryWrapper = Wrappers.lambdaQuery(OrderItemDO.class).eq(OrderItemDO::getOrderSn, orderSn);
        List<OrderItemDO> orderItemDOS = orderItemMapper.selectList(orderItemQueryWrapper);
        TicketOrderDetailRespDTO result = BeanUtil.toBean(orderDO, TicketOrderDetailRespDTO.class);
        result.setPassengerDetails(BeanUtil.copyToList(orderItemDOS, TicketOrderPassengerDetailRespDTO.class));
        return result;
    }

    @Override
    public PageResponse<TicketOrderDetailRespDTO> pageTicketOrder(TicketOrderPageQueryReqDTO requestParam) {

        LambdaQueryWrapper<OrderDO> queryWrapper = Wrappers.lambdaQuery(OrderDO.class)
                .eq(OrderDO::getUserId, requestParam.getUserId())
                .in(OrderDO::getStatus, buildOrderStatusList(requestParam.getStatusType()));
        Page<OrderDO> orderDOPage = orderMapper.selectPage(new Page<>(requestParam.getPage(), requestParam.getSize()), queryWrapper);
        PageResponse<TicketOrderDetailRespDTO> pageResult = new PageResponse<>();
        pageResult.setCurrent(orderDOPage.getCurrent());
        pageResult.setSize(orderDOPage.getSize());
        pageResult.setTotal(orderDOPage.getTotal());
        List<TicketOrderDetailRespDTO> ticketOrderDetailRespDTOList = BeanUtil.copyToList(orderDOPage.getRecords(), TicketOrderDetailRespDTO.class);
        ticketOrderDetailRespDTOList.forEach(each -> {
            LambdaQueryWrapper<OrderItemDO> orderItemQueryWrapper = Wrappers.lambdaQuery(OrderItemDO.class)
                    .eq(OrderItemDO::getOrderSn, each.getOrderSn());
            List<OrderItemDO> orderItemDOS = orderItemMapper.selectList(orderItemQueryWrapper);
            each.setPassengerDetails(BeanUtil.copyToList(orderItemDOS, TicketOrderPassengerDetailRespDTO.class));
        });
        pageResult.setRecords(ticketOrderDetailRespDTOList);
        return pageResult;
    }

    @Override
    public PageResponse<TicketOrderDetailSelfRespDTO> pageSelfTicketOrder(TicketOrderSelfPageQueryReqDTO requestParam) {
        Result<UserQueryActualRespDTO> userInfoOResult = userRemoteService.queryActualUserByUsername(UserContext.getUsername());
        if (!userInfoOResult.isSuccess()) {
            throw new RemoteException("远程用户服务异常");
        }
        String idCard = userInfoOResult.getData().getIdCard();
        Integer idType = userInfoOResult.getData().getIdType();
        LambdaQueryWrapper<OrderItemPassengerDO> orderSnQueryMapper = Wrappers.lambdaQuery(OrderItemPassengerDO.class)
                .eq(OrderItemPassengerDO::getIdType, idType)
                .eq(OrderItemPassengerDO::getIdCard, idCard)
                .orderByDesc(OrderItemPassengerDO::getCreateTime)
                .select(OrderItemPassengerDO::getOrderSn);
        Page<OrderItemPassengerDO> orderItemPassengerDOPage = orderItemPassengerMapper.selectPage(new Page<>(requestParam.getPage(), requestParam.getSize()), orderSnQueryMapper);
        PageResponse<TicketOrderDetailSelfRespDTO> pageResult = new PageResponse<>(orderItemPassengerDOPage.getCurrent(), orderItemPassengerDOPage.getSize(), orderItemPassengerDOPage.getTotal());

        List<TicketOrderDetailSelfRespDTO> ticketOrderDetailSelfRespDTOList = new ArrayList<>(orderItemPassengerDOPage.getRecords().size());
        orderItemPassengerDOPage.getRecords().forEach(each -> {
            LambdaQueryWrapper<OrderDO> queryWrapper = Wrappers.lambdaQuery(OrderDO.class)
                    .eq(OrderDO::getOrderSn, each.getOrderSn());
            OrderDO orderDO= orderMapper.selectOne(queryWrapper);
            TicketOrderDetailSelfRespDTO ticketOrderDetailSelfRespDTO = BeanUtil.toBean(orderDO, TicketOrderDetailSelfRespDTO.class);
            LambdaQueryWrapper<OrderItemDO> orderItemQueryWrapper = Wrappers.lambdaQuery(OrderItemDO.class)
                    .eq(OrderItemDO::getOrderSn, each.getOrderSn())
                    .eq(OrderItemDO::getIdType, idType)
                    .eq(OrderItemDO::getIdCard, idCard)
                    .eq(OrderItemDO::getStatus, OrderItemStatus.ALREADY_PAID.getStatus());
            OrderItemDO orderItemDO = orderItemMapper.selectOne(orderItemQueryWrapper);
            if(orderItemDO == null) return;
            ticketOrderDetailSelfRespDTO.setAmount(orderItemDO.getAmount());
            ticketOrderDetailSelfRespDTO.setSeatType(orderItemDO.getSeatType());
            ticketOrderDetailSelfRespDTO.setSeatNumber(orderItemDO.getSeatNumber());
            ticketOrderDetailSelfRespDTO.setCarriageNumber(orderItemDO.getCarriageNumber());
            ticketOrderDetailSelfRespDTO.setTicketType(orderItemDO.getTicketType());
            ticketOrderDetailSelfRespDTOList.add(ticketOrderDetailSelfRespDTO);
        });
        pageResult.setRecords(ticketOrderDetailSelfRespDTOList);
        return pageResult;
    }

    @Transactional
    @Override
    public Boolean closeTicketOrder(CloseTicketOrderReqDTO requestParam) {

        RLock lock = redissonClient.getLock(RedisKeyConstant.LOCK_ORDER_STATUS_CHANGE_PREFIX + requestParam.getOrderSn());
        lock.lock();
        try {
            LambdaQueryWrapper<OrderDO> wrapper = Wrappers.lambdaQuery(OrderDO.class)
                    .eq(OrderDO::getOrderSn, requestParam.getOrderSn())
                    .select(OrderDO::getStatus);
            OrderDO orderDO = orderMapper.selectOne(wrapper);
            if(orderDO == null) {
                throw new ServiceException("订单不存在");
            }
            if (!OrderStatus.PENDING_PAYMENT.getStatus().equals(orderDO.getStatus())) {
                return Boolean.FALSE;
            }
            OrderDO updateOrderDO = new OrderDO();
            updateOrderDO.setStatus(OrderStatus.CLOSED.getStatus());
            LambdaUpdateWrapper<OrderDO> updateWrapper = Wrappers.lambdaUpdate(OrderDO.class)
                    .eq(OrderDO::getOrderSn, requestParam.getOrderSn());
            int update = orderMapper.update(updateOrderDO, updateWrapper);
            if (!SqlHelper.retBool(update)) {
                throw new ServiceException("订单关闭失败");
            }
            OrderItemDO orderItemDO = new OrderItemDO();
            orderItemDO.setStatus(OrderItemStatus.CLOSED.getStatus());
            LambdaUpdateWrapper<OrderItemDO> itemUpdateWrapper = Wrappers.lambdaUpdate(OrderItemDO.class)
                    .eq(OrderItemDO::getOrderSn, requestParam.getOrderSn());
            int updateItemResult = orderItemMapper.update(orderItemDO, itemUpdateWrapper);
            if (!SqlHelper.retBool(updateItemResult)) {
                throw new ServiceException("订单关闭失败");
            }
        } finally {
            lock.unlock();
        }
        return Boolean.TRUE;
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void payCallback(PayResultCallbackOrderEvent payResultCallbackOrderEvent) {
        String orderSn = payResultCallbackOrderEvent.getOrderSn();
        RLock lock = redissonClient.getLock(RedisKeyConstant.LOCK_ORDER_STATUS_CHANGE_PREFIX + orderSn);
        lock.lock();
        try {
            OrderDO orderDO = orderMapper.selectOne(Wrappers.lambdaQuery(OrderDO.class).eq(OrderDO::getOrderSn, orderSn));
            if(orderDO == null || !OrderStatus.PENDING_PAYMENT.getStatus().equals(orderDO.getStatus())) {
                log.error("订单状态异常，订单号：{}", orderSn);
                if(orderDO != null && OrderStatus.CLOSED.getStatus().equals(orderDO.getStatus())) {
                    log.info("支付回调失败，执行退款");
                }
                return;
            }
            OrderDO updateOrderDO = new OrderDO();
            updateOrderDO.setId(orderDO.getId());
            updateOrderDO.setStatus(OrderStatus.ALREADY_PAID.getStatus());
            updateOrderDO.setPayType(payResultCallbackOrderEvent.getChannel());
            LocalDate localDate = payResultCallbackOrderEvent.getGmtPayment().toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
            updateOrderDO.setPayTime(LocalDateTime.of(localDate, LocalTime.MIN));
            orderMapper.updateById(updateOrderDO);

            OrderItemDO orderItemDO = new OrderItemDO();
            orderItemDO.setStatus(OrderItemStatus.ALREADY_PAID.getStatus());
            orderItemMapper.update(orderItemDO, Wrappers.lambdaUpdate(OrderItemDO.class).eq(OrderItemDO::getOrderSn, orderSn));
        } finally {
            lock.unlock();
        }
    }

    @Override
    public void refundCallback(OrderItemStatusTransition orderItemStatusTransition) {
        String orderSn = orderItemStatusTransition.getOrderSn();
        RLock lock = redissonClient.getLock(RedisKeyConstant.LOCK_ORDER_STATUS_CHANGE_PREFIX + orderSn);
        lock.lock();
        try {
            OrderDO orderDO = orderMapper.selectOne(Wrappers.lambdaQuery(OrderDO.class).eq(OrderDO::getOrderSn, orderSn));
            if(orderDO == null) {
                log.error("订单状态异常，订单号：{}", orderSn);
                log.info("支付回调失败，执行退款");
            }
            OrderDO updateOrderDO = new OrderDO();
            updateOrderDO.setId(orderDO.getId());
            updateOrderDO.setStatus(orderItemStatusTransition.getOrderStatus().getStatus());
            orderMapper.updateById(updateOrderDO);

            OrderItemDO orderItemDO = new OrderItemDO();
            orderItemDO.setStatus(orderItemStatusTransition.getOrderItemStatus().getStatus());
            List<String> nameList = orderItemStatusTransition.getOrderItemDOList().stream()
                    .map(OrderItemDO::getRealName)
                    .toList();
            LambdaUpdateWrapper<OrderItemDO> updateWrapper = Wrappers.lambdaUpdate(OrderItemDO.class)
                    .eq(OrderItemDO::getOrderSn, orderSn)
                    .in(OrderItemDO::getRealName, nameList);
            orderItemMapper.update(orderItemDO, updateWrapper);
        } finally {
            lock.unlock();
        }
    }

    private List<Integer> buildOrderStatusList(Integer statusType) {
        List<Integer> result;
        switch (statusType) {
            case 0 -> result = ListUtil.of(
                    OrderStatus.PENDING_PAYMENT.getStatus()
            );
            case 1 -> result = ListUtil.of(
                    OrderStatus.ALREADY_PAID.getStatus(),
                    OrderStatus.PARTIAL_REFUND.getStatus(),
                    OrderStatus.FULL_REFUND.getStatus()
            );
            case 2 -> result = ListUtil.of(
                    OrderStatus.COMPLETED.getStatus(),
                    OrderStatus.CLOSED.getStatus()
            );
            default -> result = new ArrayList<>();
        }
        return result;
    }
}




